module wad.browserfinder;

import core.sys.windows.windef;
import std.windows.syserror : WindowsException;
import tame.meta;
import tame.env,
tame.fixed.stackcstr,
tame.windows.charset,
tame.windows.registry,
tame.string : fromStringz;

alias StrBuilder = CStr!4096;

enum Browser : ubyte {
	None,
	Chrome,
	Edge
}

private int tryGetKey(HKEY hkey, in wchar* subKey, scope char[] data) {
	import core.stdc.stdlib;
	HKEY key;
	int err = tryOpen(hkey, subKey, key);
	if (err)
		return -err;

	uint len;
	err = tryQuery(key, null, null, len);
	if (err)
		return -err;

	auto buf = cast(wchar*)alloca(len);
	err = tryQuery(key, null, buf, len);
	if (err)
		return -err;

	const u8len = toUTF8(buf[0 .. len / wchar.sizeof], data);
	if (u8len == -1)
		return -ERROR_MORE_DATA;
	return u8len;
}

private bool isExe(in char[] path) {
	import tame.io.file,
	tame.string,
	tame.text.ascii,
	tame.unsafe.string;

	mixin TempDup!path;
	return toLower(pathd).endsWith(".exe") && getSize(path) > 1;
}

nothrow:

immutable defaultOrder = [Browser.Chrome, Browser.Edge];

auto findBrowser(ref StrBuilder s, in Browser[] order = defaultOrder) {
	const slen = s.length;
	string err;
	foreach (i, b; order) {
		if (i)
			s.length = slen;
		switch (b) {
		case Browser.Chrome:
			err = findChrome(s);
			if (!err)
				return null;
			break;
		case Browser.Edge:
			err = findEdge(s);
			if (!err)
				return null;
			break;
		default:
		}
	}
	return err;
}

auto findEdge(ref StrBuilder s) {
	const pf = getEnv!"ProgramFiles(x86)";
	if (!pf.length)
		return "ProgramFiles(x86) not found";

	s ~= pf;
	s ~= `\Microsoft\`;
	const slen = s.length;
	string err;
	foreach (i, dir; Seq!(`Edge`, `EdgeCore`, `EdgeWebView`)) {
		static if (i)
			s.length = slen;
		s ~= dir ~ `\Application`;
		err = findLastestExe!"msedgewebview2.exe"(s);
		if (s.length > slen)
			return null;
	}
	if (s.length == slen)
		return "msedgewebview2 not found";
	return err;
}

auto findChrome(ref StrBuilder s) {
	import core.sys.windows.winreg;

	string err;
	try {
		char[MAX_PATH] buf = void;
		foreach (key; Seq!(HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER)) {
			const len = tryGetKey(key, `Software\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, buf);
			if (len < 0)
				throw new WindowsException(-len);
			auto path = buf[0 .. len];
			if (isExe(path)) {
				s ~= path;
				return null;
			}
		}
	} catch (Exception e) {
		err = e.msg;
	}
	const len = s.length;
	foreach (i, env; Seq!("ProgramFiles(x86)", "ProgramFiles", "localappdata")) {
		const pf = getEnv!env;
		if (pf.length) {
			static if (i)
				s.length = len;
			s ~= pf;
			{
				const slen = s.length;
				s ~= `Google\Chrome\Application\chrome.exe`;
				try {
					if (isExe(s.str))
						return null;
				} catch (Exception) {
				}
				s.length = slen;
			}
		}
	}
	return err;
}

/// Find the latest version of an exe in a directory
auto findLastestExe(string exeName)(ref StrBuilder s) {
	import std.ascii,
	std.file;

	string ver;
	const p = s.length;
	try {
		foreach (entry; dirEntries(cast(string)s.str, "*.*.*", SpanMode.shallow)) {
			auto name = entry.name[p + 1 .. $];
			if (name[0].isDigit) {
				s ~= '\\';
				s ~= name;
				s ~= '\\';
				s ~= exeName;
				try {
					if (isExe(s.str))
						ver = name;
				} catch (Exception) {
				}
			}
			s.length = p;
		}
	} catch (Exception e) {
		return e.msg;
	}
	if (ver.length) {
		s ~= '\\';
		s ~= ver;
		s ~= '\\';
		s ~= exeName;
	}
	return null;
}
